#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Author: lonewolf
# Date: 2013-09-25 09:58:52
#
import re
import os
import shutil
import json
import time

quick_cocos2dx_root="/Users/lonewolf/Public/dev/appdev/quick-cocos2d-x/quick-3.3rc1/"
# cocos2dx_root="/Users/lonewolf/Public/dev/appdev/cocos2d-x/cocos2d-x-3.3rc0/"
frameworkPath = quick_cocos2dx_root+"quick/framework"
cppPaths=[quick_cocos2dx_root+"cocos",quick_cocos2dx_root+"extensions"]
toluaPath=quick_cocos2dx_root+"cocos/scripting/lua-bindings/auto"
# 代码片段模板
snippetTemplate = """<snippet>
    <content><![CDATA[$content]]></content>
    <tabTrigger>$trigger</tabTrigger>
    <scope>source.lua</scope>
    <description>$desc</description>
</snippet>
"""

# ===定义内容及位置===
definitionTemplate="""#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Author: lonewolf
# Date: $datetime
#
data=\"\"\"$json\"\"\"
"""
# [arr,showName,path,lineNum,type] type=1 quick type=2 cocos2dx
definitionList=[]
definitionType=1

# 保存的目录
SAVE_DIR="quick-cocos2d-x_api"

# ----------解析lua相关----------

# 解析lua框架
# root为前缀
def parseLuaDir(dir,saveDir,root):
    for item in os.listdir(dir):
        path=os.path.join(dir,item)
        if os.path.isdir(path):
            dirname=item
            childDir=os.path.join(saveDir,dirname)
            parseLuaDir(path,childDir,root+dirname+".")
        elif os.path.isfile(path) and os.path.splitext(item)[1][1:]=="lua":
            parseLua(path,saveDir,root)

# 解析lua文件
def parseLua(file,saveDir,root):
    # 文件名，不包含后缀
    filename=os.path.splitext(os.path.split(file)[1])[0]
    saveDir=os.path.join(saveDir,"z"+filename)
    f=open(file,"r")
    isClass=False
    lineNum=0
    # 是否是注释
    isComment=False
    while True:
        line=f.readline()
        if line:
            lineNum+=1
            # 注释，省略过
            m=re.match("^--\[\[--",line)
            if m:
                isComment=True
                continue
            m=re.match("^\]\]",line)
            if m:
                isComment=False
                continue
            if isComment:
                continue
            # 变量或常量c.p=111
            m=re.match("^c\.(\w+)\s*=",line)
            if m:
                handleItem(saveDir,m.group(1))
                handleDefinition(m.group(1),None,file,lineNum)
                continue
            # 类
            m=re.match("^local\s+(\w+)\s*=\s*\{\}",line)
            if m:
                handleDefinition(m.group(1),None,file,lineNum)
                continue
            m=re.match("^local\s+(\w+)\s*=\s*class\(",line)
            if m:
                handleDefinition(m.group(1),None,file,lineNum)
                continue
            m=re.match("^(\w+)\s*=\s*class\(",line)
            if m:
                handleDefinition(m.group(1),None,file,lineNum)
                continue
            # 函数
            m=re.match("^function\s+(\w+\.*\w*)\s*\((.*)\)",line)
            if m:
                handleItem(saveDir,m.group(1),m.group(2))
                handleDefinition(m.group(1),m.group(2),file,lineNum)
                continue
            # 静态变量
            m=re.match("^%s\.(\w+)"%(filename),line)
            if m:
                handleItem(saveDir,"%s.%s"%(filename,m.group(1)))
                handleDefinition(m.group(1),None,file,lineNum)
                # 一行有两个语句
                m1=re.match("^.+\;\s*%s\.(\w+)"%(filename),line)
                if m1:
                    handleItem(saveDir,"%s.%s"%(filename,m.group(1)))
                    handleDefinition(m1.group(1),None,file,lineNum)
                    # print m1.group(1)
                continue
            # 类方法
            m=re.match("^function\s+(\w+)\:(\w+)\s*\((.*)\)",line)
            if m:
                isClass=True
                method=m.group(2)
                ext=""
                if method=="ctor":
                    method=m.group(1)+".new"
                    ext=root
                handleItem(saveDir,method,m.group(3),m.group(1),ext)
                handleDefinition(m.group(2),m.group(3),file,lineNum,m.group(1)+":"+m.group(2))
                continue
        else:
            break
    f.close()

# ----------解析c++相关----------

# 保存到的object。如：{"FileUtils_getInstance":[method,params,lineNum,isStatic,saveDir,file]}
CPP_OBJ={}
# 解析c++各个目录或文件
def parseCppDirs(paths,root):
    # 建立目录
    saveDir=os.path.join(SAVE_DIR,root)
    rootLen=len(quick_cocos2dx_root)
    for path in paths:
        str1=path[rootLen:]
        # print(str1)
        sd=os.path.join(saveDir, str1)
        parseCppDir(path,sd)

# 解析c++框架 isParseDir是否解析该目录下的目录
def parseCppDir(dir,saveDir,isParseDir=True):
    # 不包含的目录
    exclude="scripting"
    for item in os.listdir(dir):
        path=os.path.join(dir,item)
        if os.path.isdir(path) and isParseDir:
            if item==exclude:
                continue
            a=True
            if item=="platform":
                a=False
            childDir=os.path.join(saveDir,item)
            parseCppDir(path,childDir,a)
        elif os.path.isfile(path) and os.path.splitext(item)[1][1:]=="h":
            parseCpp(path,saveDir)

def parseCpp(file,saveDir):
    filename=os.path.split(file)[1]
    filename=os.path.splitext(filename)[0]
    saveDir=os.path.join(saveDir, filename)
    classname=""
    enum=False
    isComment=False
    f=open(file,"r")
    lineNum=0
    while True:
        line=f.readline()
        if line: 
            lineNum=lineNum+1
            # 去掉注释行
            # 行注释
            m=re.match("^\s*/\*.*\*/$",line)
            if m:
                continue
            # 行注释
            m=re.match("^\s*//",line)
            if m:
                continue
            # 块注释结束
            m=re.match("^.*\*/$",line)
            if m:
                isComment=False 
                continue 
            if isComment:
                continue
            # 块注释
            m=re.match("^\s*/\*",line)
            if m:
                isComment=True
                continue
            # 析构
            m=re.match("^\s+~\w+",line)
            if m:
                continue
            
            # enum start
            m=re.match("^typedef\s+enum",line)
            if m:
                enum=True
                continue
            m=re.match("^enum",line)
            if m:
                enum=True   
                continue
            # enum body
            m=re.match("^\s+(\w+)\s*$",line)
            if enum and m:
                handleItem(saveDir, m.group(1), None)
                handleDefinition(m.group(1),None,file,lineNum)
                continue
            # enum enum end
            m=re.match("^\s*}\s+\w+;",line)
            if m:
                enum=False
                continue 
            m=re.match("^\s*};",line)
            if m:
                enum=False
                continue

            # 预处理，把参数中的预设值就去掉
            line=re.sub("=\s*\w+\(.*\),",",",line)
            line=re.sub("=\s*\w+\(.*\)\)",")",line)
            # class
            m=re.match("^class\s+(\w+);",line)
            if m:
                continue
            m=re.match("^class\s+(\w+)",line)
            m2=re.match("^class\s+CC_DLL\s+(\w+)",line)
            m3=re.match("^class\s+EXPORT_DLL\s+(\w+)",line)
            m4=re.match("^class\s+CC_EX_DLL\s+(\w+)",line)
            if m2:
                m=m2
            if m3:
                m=m3
            if m4:
                m=m4
            if m:
                classname=m.group(1)
                CPP_OBJ[classname]=[classname,None,lineNum,False,saveDir,file]
                continue
            # static function
            m=re.match("^\s+static\s+\S+\s+(\w+)\s*\((.*)\);",line)
            m2=re.match("^\s+static\s+\w+\W+(\w+)\s*\((.*)\);",line)
            if m2:
                m=m2
            if m:
                method=m.group(1)
                CPP_OBJ[classname+"_"+method]=[method,m.group(2),lineNum,True,saveDir,file]
                continue
            # function
            m=re.match("^\s+virtual\s+\S+\s+(\w+)\s*\((.*)\)",line)
            if m:
                method=m.group(1)
                CPP_OBJ[classname+"_"+method]=[method,m.group(2),lineNum,False,saveDir,file]
                continue
            m=re.match("^\s+virtual\s+\w+\s+\S+\s+(\w+)\s*\((.*)\)",line)
            if m:
                method=m.group(1)
                # print(method)
                CPP_OBJ[classname+"_"+method]=[method,m.group(2),lineNum,False,saveDir,file]
                continue
            m=re.match("^\s+\S+\s+(\w+)\s*\((.*)\)",line)
            if m:
                method=m.group(1)
                CPP_OBJ[classname+"_"+method]=[method,m.group(2),lineNum,False,saveDir,file]
                continue
            m=re.match("^\s+inline\s+\S+\s+(\w+)\s*\((.*)\)",line)
            if m:
                method=m.group(1)
                CPP_OBJ[classname+"_"+method]=[method,m.group(2),lineNum,False,saveDir,file]
                continue
            # define
            # m=re.match("^#define\s+(\w+)",line)
            # if m:
            #     handleItem(saveDir, m.group(1), None)
            #     handleDefinition(m.group(1),None,file,lineNum)
            #     continue
        else:
            break
    f.close()


# --------tolua部分----------

# 解析tolua
def parseToluaDir(dir):
    for item in os.listdir(dir):
        path=os.path.join(dir,item)
        if os.path.isdir(path):
            parseToluaDir(path)
        elif os.path.isfile(path) and os.path.splitext(item)[1][1:]=="cpp":
            parseTolua(path)

# 解析lua文件
def parseTolua(file):
    f=open(file,"r")
    classname=""
    while True:
        line=f.readline()
        if line:
            # 开始
            m=re.match("^\s*tolua_beginmodule\(tolua_S,\"(\w+)\"\);",line)
            if m:
                classname=m.group(1)
                if CPP_OBJ.get(classname):
                    handleTolua(classname, CPP_OBJ[classname])
                continue
            # 方法
            m=re.match("^\s*tolua_function\(tolua_S,\"(\w+)\"",line)
            if classname!="" and m:
                str1=classname+"_"+m.group(1)
                if CPP_OBJ.get(str1):
                    handleTolua(classname, CPP_OBJ[str1])
                continue
            # 结束
            m=re.match("^\s*tolua_endmodule",line)
            if m:
                classname=""
                continue
        else:
            break
    f.close()

# 处理对应的cpp
def handleTolua(classname,arr):
    method=arr[0]
    params=arr[1]
    lineNum=arr[2]
    isStatic=arr[3]
    saveDir=arr[4]
    file=arr[5]
    name=method
    desc=classname
    ext=""
    if method==classname:
        # 类声明
        desc="."
        ext="cc."
    if isStatic:
        # 静态函数
        name=classname+":"+method
        desc="."
        ext="cc."
    handleItem(saveDir, name, params, desc, ext)
    handleDefinition(name, params, file, lineNum, None)

# ---------公共函数---------

# 添加进定义里
# name 可以是函数或变量
# params 如果name是函数，还可以带参数
# file 文件所在位置
# lineNum 所在行
# showName 是显示在多项列表中的项名称，比如C++里的静态方法或变量A::myName，搜索的时候只要myName就行
def handleDefinition(name,params,file,lineNum,showName=None):
    # 从root开始的文件位置
    root=quick_cocos2dx_root
    if definitionType==2:
        root=cocos2dx_root
    path=file[len(root):]
    # print(path)
    if not showName:
        showName=name
    if params:
        showName+="("+handleParams(params)[0]+")"
    # display.newScene可以搜索整个句子，也可只搜索newScene
    arr=[]
    index=name.find(".")
    if index==-1:
        index=name.find(":")
    if index!=-1:
        str1=name[(index+1):]
        arr.append(str1)
        # print(str1)
    arr.append(name)
    # if file.rfind("Node")!=-1:
    #     print(name)
        # print(file)
    
    definitionList.append([arr,showName,path,lineNum,definitionType])

# 处理生成一个snippet
# saveDir 保存到的目录
# name 变量或函数名
# params 参数
# desc 
# ext 附加到name的存到content的字符
def handleItem(saveDir,name,params=None,desc=".",ext=""): 
    trigger=name
    content=ext+name
    paramArr=[]
    if params!=None:
        # print("aa %s"%(params))
        paramArr=handleParams(params)
        trigger+="(%s)"%(paramArr[0])
        content+="(%s)"%(paramArr[1])
    template=snippetTemplate.replace("$content",content)
    template=template.replace("$trigger",trigger)
    template=template.replace("$desc",desc)
    # 处理保存的文件名
    a=""
    if len(paramArr)>0 and len(paramArr[0])>0:
        args=paramArr[0].split(",")
        for i in range(0,len(args)):
            args[i]=re.sub("\s","",args[i])
        a="-".join(args)
        a="-"+a
    if len(a)>60:
        print(a)
        a=a[0:60]
    name=re.sub("\W","_",name)
    saveName=name+a+".sublime-snippet"
    # print saveName
    # 建立类对应的目录
    if not os.path.exists(saveDir):
        os.makedirs(saveDir)
    savePath=os.path.join(saveDir,saveName)
    f=open(savePath, "w+")
    f.write(template)
    f.close()

# 处理参数,最后返回[str1,str2]，str1是trigger里的字符串，str2是content里的
def handleParams(params):
    # if params=="":
    #     return "";
    args=[]
    for item in params.split(","):
        str1=re.sub("\W*=.*","",item)
        str1=re.sub("\s*\(.*","",str1)
        str1=re.sub(".*\W+(\w+)\W*$",r"\1",str1)
        str1=re.sub("^\**","",str1)
        str1=re.sub("\**$","",str1)
        str1=re.sub("\s","",str1)
        if str1!="" and str1!="void":
            args.append(str1)
    args2=[]
    for i in range(0,len(args)):
        args2.append("${%d:%s}"%(i+1,args[i]))
    a1=", ".join(args)
    a2=", ".join(args2)
    return [a1,a2]


# 先删除保存目录下的文件
def deleteFiles(path,root):
    if os.path.isfile(path):
        try:
            os.remove(path)
        except Exception, e:
            pass
    elif os.path.isdir(path):
        for item in os.listdir(path):
            itemsrc=os.path.join(path,item)
            deleteFiles(itemsrc,root)
        if path!=root:
            try:
                os.rmdir(path)
            except Exception, e:
                pass


# ----------主调用语句----------

# 删除之前的
deleteFiles(SAVE_DIR,SAVE_DIR)
# 解析
definitionType=1
saveDir=os.path.join(SAVE_DIR,"framework")
parseLuaDir(frameworkPath,saveDir,"")

definitionType=1
parseCppDirs(cppPaths, "cocos2d-x")
parseToluaDir(toluaPath)

# 生成定义文件  
str1=json.dumps(definitionList)
datetime=time.strftime("%Y-%m-%d %X", time.localtime())
data=definitionTemplate.replace("$datetime",datetime)
data=data.replace("$json",str1)
f=open("definition.py","w+")
f.write(data)
f.close()
